/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "ath.h"

/* handles update requests 
 * distributes to phys, farm update or returns invalid
 */
void 
ath_request_update(request_rec *r)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(r->server);
    char *str_updt;
    char *str_stat;
    char *str_stat_args;
    ath_engine *engine = ath_engine_get();
    ASSERT(r);
    ASSERT(aconf);

    engine->stats->hits_update++;

    if (r->args == NULL) /* nothing to do */
      {
	  engine->stats->hits_error++;
	  ap_rputs(ATH_EENEMPTY, r);
	  return; 
      }
    str_updt = apr_pstrndup(r->pool, r->args, strlen(r->args));

    /* now parse update request
     * is it farm or phys update else invalid
     */
    if (!strncmp(r->uri, ATH_REQ_PHYS, strlen(ATH_REQ_PHYS)))
	ath_request_update_phys(r->pool, str_updt);
    else if (!strncmp(r->uri, ATH_REQ_FARM, strlen(ATH_REQ_FARM)))
	ath_request_update_farm(r->pool, str_updt);
    else
      {
	  engine->stats->hits_error++;
	  ap_rputs(ATH_EUPINVAL, r);
      }
}

/* parses request and updates farm */
void 
ath_request_update_farm(apr_pool_t *pool, char *str_updt)
{
    char *str_tok1, *str_tok2;
    char *inter1, *inter2, *inter3;
    char *type_val, *type, *val, *adds_to, *adds, *host;
    char *sep1 = ",";
    char *sep2 = "=";
    char *sep3 = "-";
    int g, i, j, k, c, idx, addme;
    int rems = 0;
    int *rem_idx_list;
    int *orig_idx_list;
    ath_farm *farm;
    ath_phys *phys;
    ath_node *listctx1, *listctx2, *listctx3;
    ath_lock *lock;
    ath_upd_farm *farm_upd;

    ASSERT(str_updt);
    farm_upd= ath_farm_upd_alloc(pool);

    /* break on "," for field type to update */
    str_tok1 = apr_strtok(str_updt, sep1, &inter1);
    while (str_tok1 != NULL)
      {
	  /* break on "=" to get type and value */
	  type = apr_strtok(str_tok1, sep2, &inter2);
	  val = apr_strtok(NULL, sep2, &inter2);
	  c = *type;
	  if ((type != NULL) && (val != NULL))
	    {
		/* switch on type */
		switch ((char) c)
		  {
		  case ATH_CASE_F_NAME: /* farm with name */
		      if (*val == '*')
			{
			    i = 0;
			    while ((farm = ath_farm_for_idx(i)) != NULL)
			      {
				  ath_list_ap_add_r(pool, 
						    farm_upd->farms, farm, 
						    &listctx1);
				  i++;
			      }
			}
		      else
			{
			    farm = ath_farm_for_name(val);
			    if (farm != NULL)
				ath_list_ap_add_r(pool, 
						  farm_upd->farms, farm,
						  &listctx1);
			}
		      break;
		  case ATH_CASE_ON: /* online / offline */
		      farm_upd->on = ath_flag_is_on(val);
		      farm_upd->on_u = 1;
		      break;
		  case ATH_CASE_ADD_P: 
		      /* add member make sure phys exists ahead of time */
		      phys = ath_phys_for_host(val);
		      if (phys != NULL)	      
			  ath_list_ap_add_r(pool, 
					    farm_upd->member_add, phys, 
					    &listctx2);
		      break;
		  case ATH_CASE_REM_P: /* remove member */
		      phys = ath_phys_for_host(val);
		      if (phys != NULL)
			  ath_list_ap_add_r(pool, 
					    farm_upd->member_rem, phys,
					    &listctx3);
		      break;
		  case ATH_CASE_ALGO: /* change algorithm */
		      farm_upd->str_algo = val;
		      break;
		  case ATH_CASE_HITADDS: /* change hit adds behavior */
		      /* separated by "-", eg cpu-10, in input */
		      adds_to = apr_strtok(val, sep3, &inter3);
		      adds = apr_strtok(NULL, sep3, &inter3);
		      if ((adds_to != NULL) && (adds != NULL))
			{
			    farm_upd->hit_adds_to = ath_algo_str_to_type(adds_to);
			    farm_upd->hit_adds = atof(adds);
			}
		      break;
		  case ATH_CASE_EXPECT:
		      farm_upd->expect = ath_flag_is_on(val);
		      farm_upd->expect_u = 1;
		      break;
		  case ATH_CASE_DURL:
		      farm_upd->str_down_url = val;
		      break;
		  case ATH_CASE_OURL:
		      farm_upd->str_offline_url = val;
		      break;
		  case ATH_CASE_TTL:
		      farm_upd->ttl = atoi(val);
		      farm_upd->ttl_u = 1;
		      break;
		  case ATH_CASE_RTTL:
		      farm_upd->rttl = atoi(val);
		      farm_upd->rttl_u = 1;
		      break;
		  }
		str_tok1 = apr_strtok(NULL, sep1, &inter1);
	    }
      }
    /* update the farms requested */
    ath_list_rewind_r(farm_upd->farms, &listctx1); 
    while (ath_list_hasnext_r(farm_upd->farms, &listctx1))
      {
	  farm = (ath_farm *) ath_list_getnext_r(farm_upd->farms, &listctx1);
	  ath_farm_update(pool, farm, farm_upd);
      }
}

/* parse and process update phys str */
void 
ath_request_update_phys(apr_pool_t *pool, char *str_updt)
{
    char *str_tok1;
    char *inter1, *inter2;
    char *type_val, *type, *val;
    char *sep1 = ",";
    char *sep2 = "=";
    int i, j, k, c;
    ath_phys *phys;
    ath_farm *farm;
    ath_node *listctx;
    ath_engine *engine = ath_engine_get();
    int *indices;
    /* not trusting req->pool right now for non-output data */
    ath_upd_phys *phys_upd;

    ASSERT(str_updt);
    phys_upd = ath_phys_upd_alloc(pool);

    /* break on "," to find each update type */
    str_tok1 = apr_strtok(str_updt, sep1, &inter1);
    while (str_tok1 != NULL)
      {
	  /* break on "=" to get type and value */
	  type = apr_strtok(str_tok1, sep2, &inter2);
	  val = apr_strtok(NULL, sep2, &inter2);
	  c = *type;
	  if ((type != NULL) && (val != NULL))
	    {
		switch ((char) c)
		  {
		  case ATH_CASE_P_HOST:
		      if (*val == '*')
			{
			    i = 0;
			    while ((phys = ath_phys_for_idx(i)) != NULL)
			      {
				  ath_list_ap_add_r(pool, 
						    phys_upd->physs, phys,
						    &listctx);
				  i++;
			      }
			}
		      else
			{
			    phys = ath_phys_for_host(val);
			    if (phys != NULL)
				ath_list_ap_add_r(pool, 
						  phys_upd->physs, phys,
						  &listctx);
			}
		      break;
		  case ATH_CASE_ON:
		      phys_upd->on = ath_flag_is_on(val);
		      phys_upd->on_u = 1;
		      break;
		  case ATH_CASE_UP:
		      /* be careful about marking down 
		       * (ath_flag_is_on = false)
		       * is not good enough
		       */
		      if ((!strncmp(val, "down", 4))
			  || (!strncmp(val, "Down", 4))
			  || (!strncmp(val, "DOWN", 4))
			  || (!strncmp(val, "0", 1))
			  )
			  phys_upd->up = ATH_DOWN;
		      else
			  phys_upd->up = ATH_UP;
		      phys_upd->up_u = 1;
		      break;
		  case ATH_ALGO_CPU:
		      phys_upd->cpu = atof(val);
		      phys_upd->cpu_u = 1;
		      break;
		  case ATH_ALGO_NET:
		      phys_upd->net = atof(val);
		      phys_upd->net_u = 1;
		      break;
		  case ATH_ALGO_LOD:
		      phys_upd->ld = atof(val);
		      phys_upd->ld_u = 1;
		      break;
		  case ATH_ALGO_MEM:
		      phys_upd->mem = atof(val);
		      phys_upd->mem_u = 1;
		      break;
		  case ATH_ALGO_DIS:
		      phys_upd->disk = atof(val);
		      phys_upd->disk_u = 1;
		      break;
		  case ATH_ALGO_CU0:
		      phys_upd->cus0 = atof(val);
		      phys_upd->cus0_u = 1;
		      break;
		  case ATH_ALGO_CU1:
		      phys_upd->cus1 = atof(val);
		      phys_upd->cus1_u = 1;
		      break;
		  case ATH_ALGO_CU2:
		      phys_upd->cus2 = atof(val);
		      phys_upd->cus2_u = 1;
		      break;
		  case ATH_ALGO_CU3:
		      phys_upd->cus3 = atof(val);
		      phys_upd->cus3_u = 1;
		      break;
		  case ATH_ALGO_CU4:
		      phys_upd->cus4 = atof(val);
		      phys_upd->cus4_u = 1;
		      break;
		  case ATH_ALGO_CU5:
		      phys_upd->cus5 = atof(val);
		      phys_upd->cus5_u = 1;
		      break;
		  case ATH_ALGO_CU6:
		      phys_upd->cus6 = atof(val);
		      phys_upd->cus6_u = 1;
		      break;
		  case ATH_ALGO_CU7:
		      phys_upd->cus7 = atof(val);
		      phys_upd->cus7_u = 1;
		      break;
		  case ATH_ALGO_CU8:
		      phys_upd->cus8 = atof(val);
		      phys_upd->cus8_u = 1;
		      break;
		  case ATH_ALGO_CU9:
		      phys_upd->cus9 = atof(val);
		      phys_upd->cus9_u = 1;
		      break;
		  }
	    }
	  str_tok1 = apr_strtok(NULL, sep1, &inter1);
      }
    ath_list_rewind_r(phys_upd->physs, &listctx);
    while (ath_list_hasnext_r(phys_upd->physs, &listctx))
      {
	  phys = (ath_phys *) ath_list_getnext_r(phys_upd->physs, &listctx);
	  /* now we lock it */
	  ath_phys_update(phys, phys_upd);
      }
    /* we dont want to test all this inside the above lock window */
    if (engine->nominate_per_update == 1)
      {
	  /* biggest possible list is every farm, padded */
	  indices = (int *) apr_pcalloc(pool, (sizeof(int) * (engine->farm_count + 1)));
	  for (i = 0; i < engine->farm_count + 1; i++)
	      indices[i] = ATH_EOA;
	  /* get all the farms to be updated */
	  ath_list_rewind_r(phys_upd->physs, &listctx);
	  while (ath_list_hasnext_r(phys_upd->physs, &listctx))
	    {
		phys = (ath_phys *) 
		  ath_list_getnext_r(phys_upd->physs, &listctx);
		j = 0;
		while (phys->membership_idx[j] != ATH_EOA)
		  {
		      k = 0;
		      while ((indices[k] != ATH_EOA) /* find it or get EOA */
			     && (indices[k] != phys->membership_idx[j]))
			  k++;
		      if (indices[k] == ATH_EOA) /* it's not there yet */
			  indices[k] = phys->membership_idx[j];
		      j++;
		  }
	    }
	  i = 0;
	  while (indices[i] != ATH_EOA)
	    {
		k = indices[i];
		farm = ath_farm_for_idx(k);
		if (farm != NULL)
		    k = ath_algo_expect_get_chosen(pool, farm, NULL);
		if (k != ATH_EOA)
		  {
		      ath_lock *lock;
		      lock = ath_lock_for_idx(farm->lock_idx);
		      ath_lock_acquire(lock);
		      farm->member_chosen_idx = k;
		      ath_lock_release(lock);
		  }
		i++;
	    }
      }
}



/* retrieves status for subsystem farm, phys, or engine */
void 
ath_request_status(request_rec *r)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(r->server);
    char *str_stat;
    const char *str_out;
    ath_engine_stat *stats = ath_engine_stat_get();
    ASSERT(r);
    ASSERT(aconf);

    stats->hits_status++;

    if (r->args == NULL) /* nothing to do */
      {
	  stats->hits_error++;
	  ap_rputs(ATH_EENEMPTY, r);
	  return; 
      }
    str_stat = apr_pstrndup(r->pool, r->args, strlen(r->args));

    /* now parse status request */
    /* is it farm or phys status, strlen("/status") = 7 */
    if (!strncmp(r->uri, ATH_REQ_PHYS, strlen(ATH_REQ_PHYS)))
	str_out = ath_request_status_phys(r->pool, str_stat, aconf);
    else if (!strncmp(r->uri, ATH_REQ_FARM, strlen(ATH_REQ_FARM)))
	str_out = ath_request_status_farm(r->pool, str_stat, aconf);
    else if (!strncmp(r->uri, ATH_REQ_ENGINE, strlen(ATH_REQ_ENGINE)))
	str_out = ath_request_status_engine(r->pool, str_stat);
    else if (!strncmp(r->uri, ATH_REQ_DEBUG, strlen(ATH_REQ_DEBUG)))
	str_out = ath_request_status_debug(r);
    else
      {
	  stats->hits_error++;
	  ap_rputs(ATH_ESTINVAL, r);
	  return;
      }
    if (str_out != NULL)
	ap_rputs(str_out, r);
    else
      {
	  stats->hits_error++;
	  ap_rputs(ATH_ESTINVAL, r);
      }
    return;
}

/* parse phys status request and format output */
char * 
ath_request_status_phys(apr_pool_t *pool,
		       char *str_stat, ath_conf_ap *aconf)
{
    char *str_tok1;
    char *inter1, *inter2;
    char *type_val, *type, *val;
    char *sep1 = ",";
    char *sep2 = "=";
    char *hostname = NULL;
    const char *format = NULL;
    char *str_out = NULL;
    int i, c;
    ath_phys *phys;
    ath_node *listctx;
    ath_list *phys_list;

    ASSERT(str_stat);
    ASSERT(aconf);

    phys_list = (ath_list *) ath_list_ap_alloc_r(pool, &listctx);

    /* break on "," for typed req */
    str_tok1 = apr_strtok(str_stat, sep1, &inter1);
    while (str_tok1 != NULL)
      {
	  /* break on "=" for type, value */
	  type = apr_strtok(str_tok1, sep2, &inter2);
	  val = apr_strtok(NULL, sep2, &inter2);
	  c = *type;
	  if ((type != NULL) && (val != NULL))
	    {
		switch ((char) c)
		  {
		  case ATH_CASE_P_HOST:
		      /* allow fetching all at once */
		      if (*val == '*')
			{
			    i = 0;
			    while ((phys = ath_phys_for_idx(i)) != NULL)
			      {
				  ath_list_ap_add_r(pool, phys_list, phys, 
						    &listctx);
				  i++;
			      }
			}
		      else
			{
			    /* also allow subset of all */
			    phys = ath_phys_for_host(val);
			    if (phys != NULL)
				ath_list_ap_add_r(pool, phys_list, phys, 
						  &listctx);
			}
		      break;
		  case ATH_CASE_FMT:
		      format = val;
		      break;
		  }
	    }
	  str_tok1 = apr_strtok(NULL, sep1, &inter1);
      }
    if (ath_list_size(phys_list) > 0)
	str_out = ""; /* so its not null */
    ath_list_rewind_r(phys_list, &listctx);
    while (ath_list_hasnext_r(phys_list, &listctx))
      {
	  phys = (ath_phys *) ath_list_getnext_r(phys_list, &listctx);
	  /* no format or "*" format sets it to default */
	  if ((format == NULL) || (*format == '*'))
	      format = aconf->engine_status_physfmt;
	  str_out = 
	    apr_pstrcat(pool, str_out,
			(ath_phys_to_str(pool, phys, format)),
			"\n", NULL);
      }
    if (str_out != NULL)
	return str_out;
    return ATH_ESTIPHYS;
}

/* parse and process farm status request */
char * 
ath_request_status_farm(apr_pool_t *pool,
		       char *str_stat, ath_conf_ap *aconf)
{
    char *str_tok1;
    char *inter1, *inter2;
    char *type_val, *type, *val;
    char *sep1 = ",";
    char *sep2 = "=";
    char *name = NULL;
    const char *format = NULL;
    char *str_out = NULL;
    int i, c;
    ath_farm *farm;
    ath_node *listctx;
    ath_list *farm_list;

    ASSERT(str_stat);
    ASSERT(aconf);

    farm_list = (ath_list *) ath_list_ap_alloc_r(pool, &listctx);
    /* break on ',' for type/val pairs */
    str_tok1 = apr_strtok(str_stat, sep1, &inter1);
    while (str_tok1 != NULL)
      {
	  /* break on '=' for type and value */
	  type = apr_strtok(str_tok1, sep2, &inter2);
	  val = apr_strtok(NULL, sep2, &inter2);
	  c = *type;
	  if ((type != NULL) && (val != NULL))
	    {
		switch ((char) c)
		  {
		  case ATH_CASE_F_NAME:
		      if (*val == '*') /* permit all by * */
			{
			    i = 0;
			    while ((farm = ath_farm_for_idx(i)) != NULL)
			      {
				  ath_list_ap_add_r(pool, farm_list, farm,
						    &listctx);
				  i++;
			      }
			}
		      else
			{
			    /* also allow subset */
			    farm = ath_farm_for_name(val);
			    if (farm != NULL)
				ath_list_ap_add_r(pool, farm_list, farm, 
						  &listctx);
			}  break;
		  case ATH_CASE_FMT:
		      format = val;
		      break;
		  }
	    }
	  str_tok1 = apr_strtok(NULL, sep1, &inter1);
      }
    if (ath_list_size(farm_list) > 0)
	str_out = ""; /* so its not null */
    ath_list_rewind_r(farm_list, &listctx);
    while (ath_list_hasnext_r(farm_list, &listctx))
      {
	  farm = (ath_farm *) ath_list_getnext_r(farm_list, &listctx);
	  if ((format == NULL) 
	      || (*format == 'a') || (*format == 'A') || (*format == '*'))
	      format = aconf->engine_status_farmfmt;
	  str_out = 
	    apr_pstrcat(pool, str_out,
			(ath_farm_to_str(pool, farm, format)),
			"\n", NULL);
      }
    if (str_out != NULL)
	return str_out;
    return ATH_ESTIFARM;
}


/* just dumps all useful info at once
 * this may get removed since it can mimiced by embedded
 * status requests (except aconf params)
 */
char * 
ath_request_status_debug(request_rec *r)
{
#ifdef ATH_DEBUG_STATUS_FUNC
    int i, j, idx;
    ath_conf_ap *aconf = ATH_CONF_AP_GET(r->server);
    ath_engine *engine = ath_engine_get();
    ath_algo_def *engalg = engine->algo_def;
    ath_farm *farm;
    ath_phys *phys;
    ath_node *listctx1, *listctx2;
    ath_lock *lock;
    char *str_out = "CONFIG and REQUEST:";  
    ASSERT(r);
    ASSERT(aconf);

    str_out = 
      apr_pstrcat(r->pool, str_out, 
		  "\nconf->shm_path: ", aconf->shm_path, 
		  "\nconf->mux_prefix: ", aconf->mux_prefix,
		  "\nconf->phys_count: ", (apr_psprintf(r->pool, 
							"%d", aconf->phys_count)),
		  "\nconf->farm_count:  ", (apr_psprintf(r->pool,
							 "%d", aconf->farm_count)),
		  "\nrequest->uri:  ", r->uri,
		  "\nrequest->args: ", NULL);
    if (r->args)
	str_out = apr_pstrcat(r->pool, str_out, r->args, NULL);
    str_out = apr_pstrcat(r->pool, str_out, "\n\nFARMS:", NULL);
    i = 0;
    farm = ath_farm_for_idx(i);
    while (farm)
      {
	  str_out = apr_pstrcat(r->pool, str_out, "\n",
				ath_farm_to_str(r->pool, 
						farm, aconf->engine_status_farmfmt),
				NULL);
	  i++;
	  farm = ath_farm_for_idx(i);
      }
    str_out = apr_pstrcat(r->pool, str_out, "\nPHYSICALS:", NULL);
    i = 0;
    phys = ath_phys_for_idx(i);
    while (phys)
      {
	  str_out = apr_pstrcat(r->pool, str_out, "\n",
				ath_phys_to_str(r->pool, 
						phys, aconf->engine_status_physfmt),
				NULL);
	  i++;
	  phys = ath_phys_for_idx(i);
      }
    str_out = apr_pstrcat(r->pool, str_out, "\n\nENGINE:\n", 
			  ath_engine_to_str(r->pool, 
					    aconf->engine_status_engifmt), 
			  "\n", NULL);
#else
    char *str_out = NULL;
    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, ATH_ESTNDEBU);
#endif // ATH_DEBUG_STATUS_FUNC
    return str_out;
}

/* dumps info contained in engine obj
 */
char * 
ath_request_status_engine(apr_pool_t *pool, char *str_stat)
{
    char *str_out = NULL;
    /* str_stat should containg the args, 
     * which is only the format (no f=) 
     */
    str_out = ath_engine_to_str(pool, str_stat);
    return str_out;
}


/* handles a request passed via the reverse proxy
 * looks for farm name in URL's host position
 * substitutes with balanced member
 */
void 
ath_request_balance_proxyreq(request_rec *r)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(r->server);
    ath_lock *lock;
    char *prox_uri, *prox_proto, *prox_portfile, *str_farm;
    int chosen_idx = ATH_EOA;
    int i, j;
    ath_engine *engine = ath_engine_get();

    ASSERT(r);
    ASSERT(aconf);

    engine->stats->hits_balance++;

    /* this is the basic pattern we're working with:
     * proxy:http://STR_FARM/badflskdjflskdf
     */
    prox_uri = r->filename;
    i = 0;
    /* advance to first : */
    while (((prox_uri + i) != NULL) && (*(prox_uri + i) != ':'))
	i++;
    if ((prox_uri + i) != NULL)
	i++;
    /* advance to second : */
    while (((prox_uri + i) != NULL) && (*(prox_uri + i) != ':'))
	i++;
    /* advance to first "/" */
    while (((prox_uri + i) != NULL) && (*(prox_uri + i) != '/'))
	i++;
    /* move past 2nd "/" */
    if ((prox_uri + i) != NULL)
	i = i + 2;
    /* i now indicates host (str_farm) offset from string head */
    prox_proto = apr_pstrndup(r->pool, prox_uri, i);
    /* move to next slash */
    prox_uri = prox_uri + i;
    j = 0;
    while (((prox_uri + j) != NULL) /* stops at port or / */
	   && (*(prox_uri + j) != '/')  /* catch end of name at / */
	   && (*(prox_uri + j) != ':')) /* catch end of name at : */
	j++;
    if ((prox_uri != NULL) && (prox_uri + j != NULL))
      {
	  str_farm = apr_pstrndup(r->pool, prox_uri, j);
	  prox_portfile = 
	    apr_pstrndup(r->pool, prox_uri + j, strlen(prox_uri + j));
      }
    if ((prox_proto != NULL) && (str_farm != NULL) && (prox_portfile != NULL)) 
      {
	  ath_farm *farm = NULL;
	  /* find the farm if it exists */
	  if (aconf->smartfarm_enable)
	    {
		ath_smartfarm_handler(r, &str_farm, prox_proto, prox_portfile);
		/* if req is done here str_farm is set to NULL */
	    }

	  ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
			"farm lookup: %s", str_farm);

	  if (str_farm)
	      farm = ath_farm_for_name(str_farm);

	  if (farm != NULL)
	    {
		/* OK now we actually get the load balanced hostname */
		if (farm->on == ATH_ON)
		  {
		      ath_phys *phys = NULL;
		      chosen_idx = 
			ath_algo_expect_get_chosen(r->pool, farm, NULL);
		      phys = ath_farm_member_for_idx(farm, chosen_idx);
		      if ((phys) && (phys->host[0] != (int) NULL))
			{
			    /* will check if hit adds is configured */
			    if ((!(aconf->read_only))
				&& (farm->hit_adds_to != (int) ATH_ALGO_NON))
				/* see function hit_adds for expalanation */
				ath_algo_hit_adds(farm, phys);
			    /* success, now rebuild the proxy request */
			    r->filename = 
			      apr_pstrcat(r->pool, 
					  prox_proto, phys->host, 
					  prox_portfile, 
					  NULL);
			    /* record hits */
			    engine->stats->hits_idx_farm[farm->idx]++;
			    engine->stats->hits_idx_phys[phys->idx]++;
			}
		      /* if down_url is configured, set target to that 
		       * occurs if physicals are down OR offline
		       */
		      else if (farm->down_url[0] != (int) NULL)
			{
			    r->filename = apr_pstrcat(r->pool, "proxy:", 
						      farm->down_url, NULL);
			    /* for GET from POST (down page is static, some webservers complain if 
			     * POST to them
			     */
			    if (aconf->engine_down_url_force_get) {
				r->method = "GET";
				r->method_number = M_GET;
			    }
			    engine->stats->hits_downurl++;
			}
		  }	
		/* if farm is offline and url exists, else fall through */
		else if (farm->offline_url[0] != (int) NULL)
		  {
		      r->filename = apr_pstrcat(r->pool, "proxy:", 
						farm->offline_url, NULL);
		      /* for GET from POST (down page is static, some webservers complain if 
		       * POST to them
		       */
		      if (aconf->engine_offline_url_force_get) {
			  r->method = "GET";
			  r->method_number = M_GET;
		      }
		      engine->stats->hits_offlineurl++;
		  }
	    }
      }
}

/* handles requests to .../balance?farm1,farm2....
 * allows viewing of current load balancer algorithm choices
 * can be used by other apps that need to retrieve a 
 * loadbalanced host 
 * accepts a comma separated list of farms
 */
void 
ath_request_balance_req(request_rec *r)
{
    ath_conf_ap *aconf = ATH_CONF_AP_GET(r->server);
    ath_farm *farm;
    ath_phys *phys;
    char *str_balance_req = NULL;
    char *str_farm_names = NULL;
    char *str_algo = NULL;
    char *str_farm;
    char *str_sep1 = ":";
    char *str_sep2 = ",";
    char *inter1, *inter2;
    int chosen_idx;
    ath_algo_def *algo_def = NULL;
    ath_engine *engine = ath_engine_get();
    ath_node *list_ctx;
    ath_list *farm_list;

    ASSERT(r);
    ASSERT(aconf);

    if (r->args != NULL)
	str_balance_req = apr_pstrndup(r->pool, r->args, strlen(r->args));
    else
      {
	  engine->stats->hits_error++;
	  ap_rputs(ATH_EBLNFARM, r);
	  return;
      }
  
    /* you can pass in one, comma sep list, or * to build list of farms */
    /* the ":" sep allows overriding the algo configed for farms */
    /* only one ":" can be specified, and it applies to all farms in list */

    /* detach an alternate algo type request */
    str_farm_names = apr_strtok(str_balance_req, str_sep1, &inter1);
    /* since args != null, str_farm_names != null */
    str_algo = apr_strtok(NULL, str_sep1, &inter1);
    /* there is no check if this is a reasonable string algo def */
    if (str_algo != NULL)
      {
	  algo_def = ath_algo_def_alloc(r->pool);
	  ath_algo_def_from_str(algo_def, str_algo);
      }

    farm_list = (ath_list *) ath_list_ap_alloc_r(r->pool, &list_ctx);
    str_farm = apr_strtok(str_farm_names, str_sep2, &inter2);
    while (str_farm != NULL)
      {
	  if (*str_farm == '*') /* glob for all farms config'ed */
	    {
		int i = 0;
		while ((farm = ath_farm_for_idx(i)) != NULL)
		  {
		      ath_list_ap_add_r(r->pool, farm_list, farm, &list_ctx);
		      i++;
		  }
	    }
	  else
	    {
		farm = ath_farm_for_name(str_farm);
		if (farm != NULL)
		    ath_list_ap_add_r(r->pool, farm_list, farm, &list_ctx);
	    }
	  str_farm = apr_strtok(NULL, str_sep2, &inter2);
      }


    if (ath_list_size(farm_list) == 0)
	return;
    else
	engine->stats->hits_balance++;

    ath_list_rewind_r(farm_list, &list_ctx);
    while (ath_list_hasnext_r(farm_list, &list_ctx))
      {
	  farm = (ath_farm *) ath_list_getnext_r(farm_list, &list_ctx);
	  if (farm->on == ATH_ON)
	      /* balancing section BEGIN */
	      /* algo_def is usually null here, unless overridden */
	      chosen_idx = 
		ath_algo_expect_get_chosen(r->pool, farm, algo_def);
	  /* balancing section end */
	  phys = ath_farm_member_for_idx(farm, chosen_idx);
	  if (phys != NULL)
	    {
		/* will check if hit adds is configured */
		if ((!(aconf->read_only))
		    && (farm->hit_adds_to != (int) ATH_ALGO_NON))
		  {
		      /* see function hit_adds for expalanation */
		      ath_algo_hit_adds(farm, phys);
		  }
		ap_rputs(phys->host, r);
		ap_rputs("\n", r); /* new line separates each result */
	    }
	  else
	    {
		engine->stats->hits_error++;
		ap_rputs(ATH_EBEMFARM, r);
	    }
      }
    return;
}

/* takes a reques for single phys or farm at a time
 * deserializes it
 * use embedded multi requests to efficiently handle
 * full mirrors
 */
void 
ath_request_mirror(request_rec *r)
{
    char *str_mirror;
    ath_engine_stat *stats = ath_engine_stat_get();

    stats->hits_mirror++;

    if ((r->args != NULL) 
	&& (!strncmp(r->uri, ATH_REQ_PHYS, strlen(ATH_REQ_PHYS))))
      {
	  str_mirror = apr_pstrndup(r->pool, r->args, strlen(r->args));
	  ath_phys_from_str(r->pool, str_mirror);
      }
    else if ((r->args != NULL) 
	     && (!strncmp(r->uri, ATH_REQ_FARM, strlen(ATH_REQ_FARM))))
      {
	  str_mirror = apr_pstrndup(r->pool, r->args, strlen(r->args));
	  ath_farm_from_str(r->pool, str_mirror);
      }
    else
      {
	  stats->hits_error++;
	  ap_rputs(ATH_EMRINVAL, r);
	  return;
      }
}

/* simple hello */
void
ath_request_hello(request_rec *r)
{
    ath_engine_stat *stats = ath_engine_stat_get();
    stats->hits_hello++;
    /* skips the initial slash */
    ap_rputs(ATH_REQ_HELLO + 1, r);
    ap_rputs("\n", r);
    return;
}


